#include "pch.h"
#include "Effect.h"
#include "GraphicsDeviceContext.h"

using namespace std;
using namespace Dx;
using namespace XamlFx;
using namespace D2D1;
using namespace Microsoft::WRL;
using namespace concurrency;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::Storage::Streams;
using namespace Windows::Graphics::Display;
using namespace Windows::Foundation;
using namespace Platform;

const unsigned SOURCE_BYTES_PER_PIXEL = 4;

Effect::Effect() : m_isValid(false)
{
    Padding = Thickness(0.0);
    m_renderTargetBitmap = ref new RenderTargetBitmap();

    auto displayInformation = DisplayInformation::GetForCurrentView();

    displayInformation->DpiChanged += ref new TypedEventHandler<DisplayInformation ^, Object ^>(this, &Effect::OnDpiChanged);

    m_logicalDpi = displayInformation->LogicalDpi;
}

Effect::~Effect()
{
}

void Effect::Invalidate()
{
    m_isValid = false;
}

bool Effect::IsValid()
{
    return m_isValid;
}

void Effect::SetUIElement(UIElement^ element)
{
    if (element == m_element)
    {
        return;
    }

    m_element = element;
}

void Effect::SetGraphicsContext(GraphicsDeviceContextPtr const& gfx)
{
    m_gfx = gfx;

    if (!m_gfx)
    {
        m_sourceBitmap = nullptr;
        m_element      = nullptr;
        //do other cleanup
        return;
    }

    OnInitialized();
}

auto Effect::ApplyEffectAsync(dxgi::Surface const& renderTargetSurface, Dx::Point2F offset) -> task<void>
{
    return create_task(m_renderTargetBitmap->GetPixelsAsync()).then([this, renderTargetSurface, offset](IBuffer^ buffer)
    {
        ComPtr<IBufferByteAccess> pBuffer;
        reinterpret_cast<IUnknown*>(buffer)->QueryInterface(IID_PPV_ARGS(&pBuffer));

        byte * pData;
        pBuffer->Buffer(&pData);

        auto props = d2d::BitmapProperties(Dx::PixelFormat(Dxgi::Format::Unknown, AlphaMode::Premultipled), m_logicalDpi, m_logicalDpi);

        auto& d2dDeviceContext = m_gfx->GetD2DDeviceContext();

        d2d::Bitmap d2dRenderTargetBitmap = d2dDeviceContext.CreateSharedBitmap(renderTargetSurface, props);
        
        EnsureSourceBitmap();
        auto sourceBitmap = m_sourceBitmap;

        if (!sourceBitmap)
        {
            return;
        }

        sourceBitmap.CopyFromMemory(pData, m_rtbSize.Width * SOURCE_BYTES_PER_PIXEL);

        d2d::Effect effect = GetEffect(sourceBitmap);

        if (!effect)
        {
            return;
        }

        auto xform = Matrix3x2F::Translation(offset.X, offset.Y);

        d2dDeviceContext.SetTarget(d2dRenderTargetBitmap);
        d2dDeviceContext.BeginDraw();
        d2dDeviceContext.Clear();
        d2dDeviceContext.SetTransform(xform);
        d2dDeviceContext->DrawImage(effect.Get());
        d2dDeviceContext.EndDraw();
        m_isValid = true;
    }, task_continuation_context::use_arbitrary());
}

auto Effect::RenderAsync() -> task<Dx::SizeU> 
{
    if (!m_element)
    {
        return create_task([this]
        {
            Dx::SizeU size(0, 0);
            m_rtbSize = size;
            return size;
        });
    }
   
    return create_task(m_renderTargetBitmap->RenderAsync(m_element)).then([this]
    {
        Dx::SizeU size(m_renderTargetBitmap->PixelWidth, 
                       m_renderTargetBitmap->PixelHeight);

        m_rtbSize = size;
        return size;
    });
}

auto Effect::GetElementPixelSize() -> Dx::SizeU
{
    return m_rtbSize;
}

void Effect::ResetResources()
{
     m_sourceBitmap = nullptr;
     m_isRendering = false;
}

auto Effect::GetLogicalDpi() -> float
{
    if (!m_gfx)
    {
        return 96.0f;
    }

    float x, y;
    m_gfx->GetD2DDeviceContext().GetDpi(x, y);
    return x;
}

auto Effect::CreateEffect(IID const& effectId) -> d2d::Effect
{
    Direct2D::DeviceContext& d2dDeviceContext = m_gfx->GetD2DDeviceContext();

    return d2dDeviceContext.CreateEffect(effectId);
}

void Effect::EnsureSourceBitmap()
{
    if (!memcmp(&m_rtbSize, &m_lastSize, sizeof(Dx::SizeU)) && m_sourceBitmap)
    {
        return;
    }

    auto& d2dDeviceContext = m_gfx->GetD2DDeviceContext();

    auto props = d2d::BitmapProperties(Dx::PixelFormat(Dxgi::Format::B8G8R8A8_UNORM, AlphaMode::Premultipled), m_logicalDpi, m_logicalDpi);

    m_sourceBitmap = d2dDeviceContext.CreateBitmap(m_rtbSize, props);

#if _DEBUG
    auto debug = ref new Platform::String();
    debug = "Creating d2d bitmap: " + m_rtbSize.Width.ToString() + "x" + m_rtbSize.Height.ToString() + "\n";
    OutputDebugString(debug->Data());
#endif
    m_lastSize = m_rtbSize;
}

void Effect::OnDpiChanged(DisplayInformation ^sender, Object ^args)
{
    m_logicalDpi = sender->LogicalDpi;
}
